<?php

//下任务不在支持修改，只支持删除
class Task extends Base
{
    const MIN_INTERVAL_SECONDS = [
        'like' => 240,
        'follow' => 80,
        'comment' => 300,
        'exposure' => 60,
        'collect' => 120,
    ];

    //todo 这里需要优化，间隔机制不够线性
    function intervalSeconds(string $action, int $seconds = 30): int
    {
        $minInterval = self::MIN_INTERVAL_SECONDS[$action];
        if ($seconds > 0 && $seconds >= $minInterval) {
            $seconds = random_int(intval($seconds * 0.8), intval($seconds * 1.5));
            return max($seconds, $minInterval);
        }

        return random_int($minInterval, $minInterval * random_int(1, 2));
    }
    function afterDelete(int $taskId = 0)
    {
        if ($taskId <= 0) return false;

        $this->mysql->task("WHERE id = ?s", $taskId)->update(['deleted_at' => date('Y-m-d H:i:s')]);
        $this->mysql->task_detail("WHERE task_id = ?s", $taskId)->update(['deleted_at' => date('Y-m-d H:i:s')]);
        $task = $this->mysql->task("WHERE id = ?i",$taskId)->array();
        $this->optimizeTaskExecuteTime($task['hash']);
        return true;
    }

    function afterAdd(int $taskId = 0)
    {
        if ($taskId <= 0) return false;

        // 避免重复提交
        $this->mysql->task_detail("WHERE task_id = ?s", $taskId)->update(['deleted_at' => date('Y-m-d H:i:s')]);

        $task = $this->mysql->task("WHERE id = ?s", $taskId)->array();
        if (empty($task) || $task['status'] != 'pending') return false;

        $excuteTimes = intval($task['times'] ?? 0);
        $comments = [];
        if ($task['action'] == 'comment') {
            $comments = array_filter(explode("\n", trim($task['content'] ?? '')));
            $excuteTimes = count($comments);
        }

        $intervalTime = intval($task['interval_time'] ?? 0);
        $lastExcute = $this->mysql->task_detail("WHERE hash = ?s AND execution_status = 'pending' AND deleted_at IS NULL ORDER BY excute_at DESC LIMIT 1", $task['hash'])->array();
        $lastExcuteTime = intval(strtotime($lastExcute['excute_at'] ?? ""));
        $startExcuteTime = $lastExcuteTime < $this->currentTime - self::MIN_INTERVAL_SECONDS[$task['action']]
            ? $this->currentTime
            : $lastExcuteTime + $this->intervalSeconds($task['action'], $intervalTime);

        for ($i = 0; $i < $excuteTimes; $i++) {
            $startExcuteTime += $i == 0 ? 0 : $this->intervalSeconds($task['action'], $intervalTime);
            $this->mysql->task_detail->insert([
                'task_id' => $taskId,
                'link' => $task['link'],
                'hash' => $task['hash'],
                'action' => $task['action'],
                'content' => $comments[$i] ?? '',
                'excute_at' => date("Y-m-d H:i:s", $startExcuteTime),
                'excute_uid' => 0,
                'group_id' => $task['group_id'],
                'execution_status' => 'pending',
                'verification_status' => 'unchecked',
                'create_at' => date('Y-m-d H:i:s'),
                'city' => $task['city'] ?? '',
                'mark' => '',
            ]);
        }

        $this->mysql->task("WHERE id = ?s", $taskId)->update(['status' => 'running']);

        //todo  这里优化合并处理
        $this->optimizeTaskExecuteTime($task['hash']);
        return true;
    }

    function get(int $limit = 1,int $forceUid = 0): array
    {
        // $tasklist = $this->mysql->task_detail("WHERE execution_status = 'pending' AND excute_at <= ?s AND deleted_at IS NULL ORDER BY excute_at ASC LIMIT ?i", date("Y-m-d H:i:s"), $limit)
        //     ->select("id,excute_at,task_id,link,type,action,group_id,hash,excute_uid,city,content")
        //     ->all();

        $sql = sprintf(
            "select a.id,a.excute_at,a.task_id,a.link,a.type,a.action,a.group_id,a.hash,a.excute_uid,a.city,a.content
            from task_detail a
            left join task b on a.task_id = b.id
            where a.execution_status = 'pending' and a.excute_at <= '%s' and a.deleted_at is null and b.deleted_at is null and b.status = 'running'
            order by a.excute_at asc limit %d",
            date("Y-m-d H:i:s"),
            $limit
        );
        $tasklist = ($this->mysql)($sql)->all();
        $excuteStatus = "failed";
        $rs = [];
        foreach ($tasklist as $task) {
            $data = $this->webapp->user->get($task['type'], $task['link'], $task['action'], $task['group_id'],  trim(intval($task['city'] ?? 0) > 100000 ? substr($task['city'], 0, 3) : $task['city']),$forceUid);
            if (isset($data['user']) && !empty($data['user'])) {
                unset($data['user']['fingerprint']);
                $task['user'] = $data['user'];
                $this->mysql->task_detail("WHERE id = ?s", $task['id'])->update([
                    'excute_uid' => $data['user']['id'],
                    'execution_status' => 'running',
                    'push_at' => date('Y-m-d H:i:s'), //记录推送时间，限定时间内未执行完毕则重新推送
                ]);

                $rs[] = $task;
                $excuteStatus = "success";
            } else {
                ($this->mysql)("UPDATE task_detail SET excute_at = DATE_ADD(excute_at, INTERVAL 5 SECOND) WHERE `hash` = ?s AND execution_status = 'pending'", $task['hash']);
            }


            //记录日志
            $this->mysql->log->insert([
                'task_id' => $task['task_id'],
                'task_detail_id' => $task['id'],
                'user_id' => $data['user']['id'] ?? 0,
                'status' => $excuteStatus,
                'info' => $data['info'] ?? '',
                'create_at' => date('Y-m-d H:i:s'),
            ]);
        }



        return $rs;
    }

    function back(int $taskDetailId = 0)
    {
        if ($taskDetailId <= 0) return false;
        $taskDetail = $this->mysql->task_detail("WHERE id = ?s AND execution_status = 'running' ", $taskDetailId)->array();
        if (empty($taskDetail)) return false;

        $this->mysql->task_detail("WHERE id = ?s", $taskDetailId)->update([
            'execution_status' => 'pending',
        ]);

        ($this->mysql)("UPDATE task_detail SET excute_at = DATE_ADD(excute_at, INTERVAL 5 SECOND) WHERE `hash` = ?s AND execution_status = 'pending'", $taskDetail['hash']);

        return true;
    }

    function update(int $taskId = 0, array $options = [])
    {
        if ($taskId <= 0) return false;
        return $this->mysql->task("WHERE id = ?s", $taskId)->update($options);
    }

    function updateTaskDetail(int $id = 0, array $options = [])
    {
        if ($id <= 0) return false;

        return $this->mysql->task_detail("WHERE id = ?s", $id)->update($options);
    }


    //优化任务执行时间，避免任务堆积
    function optimizeTaskExecuteTime(string $hash = '')
    {
        if (empty($hash)) return false;

        $cond = [
            "select a.id,a.excute_at,a.action,b.interval_time
               from task_detail a
               left join task b on a.task_id = b.id
               where a.hash = ?s and  a.execution_status = 'pending' and a.deleted_at is null and b.deleted_at is null and b.status = 'running'
               order by a.excute_at asc",
            $hash
        ];

        $taskDetails = ($this->mysql)(...$cond)->all();

        if (empty($taskDetails)) return false;

        $datas = [];
        $lastExcuteTime = 0;
        foreach ($taskDetails as $item) {
            $datas[$item['action']][] = $item;
            $lastExcuteTime = min($lastExcuteTime, strtotime($item['excute_at']));
        }

        $maxCount = 0;
        foreach ($datas as $items) {
            $maxCount = max($maxCount, count($items));
        }

        //取self::MIN_INTERVAL_SECONDS最小值

        $newExcuteAt = $this->currentTime - 100;
        for ($i = 0; $i < $maxCount; $i++) {
            foreach (array_keys($datas) as $action) {
                if (isset($datas[$action][$i])) {
                    $item = $datas[$action][$i];
                    $newExcuteAt = date("Y-m-d H:i:s", $newExcuteAt + $this->intervalSeconds($action, intval($item['interval_time'] ?? 0)));
                    $this->mysql->task_detail("WHERE id = ?s", $item['id'])->update(['excute_at' => $newExcuteAt]);
                    $newExcuteAt = strtotime($newExcuteAt);
                }
            }
        }
        return true;
    }


    //检查是否有停置了的任务，如果有，则重新排队
    function checkTaskDetail()
    {
        if ($this->rds->exists("task_detail_check_lock")) return false;

        $this->rds->set("task_detail_check_lock", 1); //30秒锁定，防止多次执行
        $this->rds->expire("task_detail_check_lock", 30);

        $sql = sprintf(
            "select a.excute_at,a.id
            from task_detail a
            left join task b on a.task_id = b.id
            where a.execution_status = 'pending' and a.excute_at <= '%s' and a.deleted_at is null and b.deleted_at is null and b.status = 'running'
            order by a.excute_at asc limit 1",
            date("Y-m-d H:i:s"),
        );
        $rs = ($this->mysql)($sql)->array();
        if ($rs && isset($rs['excute_at']) && !empty($rs['excute_at'])) {
            //超过5分钟没有执行的任务，重新排队
            if (($diffSeconds = $this->currentTime - strtotime($rs['excute_at'])) > 300) {
                ($this->mysql)("UPDATE task_detail SET excute_at = DATE_ADD(excute_at, INTERVAL {$diffSeconds} SECOND) WHERE `execution_status` = 'pending' and deleted_at is null");
            }
        }

        $this->rds->del("task_detail_check_lock");
        return true;
    }

    //超过10分钟还没有执行完的任务，全部重新排队
    function backTimeoutTask()
    {
        ($this->mysql)("UPDATE task_detail SET `execution_status` = 'pending',excute_at = DATE_ADD(excute_at, INTERVAL 600 SECOND) WHERE deleted_at is null AND  `execution_status` in ('running','failed')  AND TIMESTAMPDIFF(SECOND, push_at, NOW()) > 600");
    }
}
